package com.lifang.framework.aop.framework.support;

import cn.hutool.core.annotation.AnnotationUtil;
import com.lifang.framework.util.AnnotationUtils;
import com.lifang.framework.aop.AspectJAdvisorFactory;
import org.aspectj.lang.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {

    private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[]{
            Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};

    @Override
    public boolean isAspect(Class<?> clazz) {
        return AnnotationUtil.hasAnnotation(clazz, Aspect.class);
    }

    @SuppressWarnings("unchecked")
    protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
        for (Class<?> annotationClass : ASPECTJ_ANNOTATION_CLASSES) {
            AspectJAnnotation<?> annotation = findAnnotation(method, (Class<Annotation>) annotationClass);
            if (annotation != null) {
                return annotation;
            }
        }
        return null;
    }

    private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
        A annotation = AnnotationUtil.getAnnotation(method, toLookFor);
        if (annotation != null) {
            return new AspectJAnnotation<>(annotation);
        }
        return null;
    }

    /**
     * aspectJ 注解类型
     */
    protected enum AspectJAnnotationType {
        AtPoint, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing
    }

    /**
     * aspectJ 注解支持
     */
    protected static class AspectJAnnotation<A extends Annotation> {

        private static final String[] EXPRESSION_ATTRIBUTES = new String[]{"pointcut", "value"};

        private static final Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(5);

        private final A annotation;

        private final AspectJAnnotationType annotationType;

        private final String pointcutExpression;

        static {
            annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
            annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
            annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
            annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
            annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
        }

        public AspectJAnnotation(A annotation) {
            this.annotation = annotation;
            this.annotationType = determineAnnotationType(annotation);
            try {
                this.pointcutExpression = resolveExpression(annotation);
            } catch (Exception ex) {
                throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
            }
        }

        /**
         * 分解注解的类型
         * 转换成 相对应的AspectJAnnotationType
         */
        private AspectJAnnotationType determineAnnotationType(A annotation) {
            AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType());
            if (type != null) {
                return type;
            }
            throw new IllegalStateException("Unknown annotation type: " + annotation);
        }

        /**
         * 解析并获取注解的expression表达式
         */
        private String resolveExpression(A annotation) {
            for (String attribute : EXPRESSION_ATTRIBUTES) {
                Object value = AnnotationUtils.getValue(annotation, attribute);
                if (value instanceof String) {
                    String str = (String) value;
                    if (!str.isEmpty()) {
                        return str;
                    }
                }
            }
            throw new IllegalStateException("Failed to resolve expression: " + annotation);
        }

        public A getAnnotation() {
            return annotation;
        }

        public AspectJAnnotationType getAnnotationType() {
            return annotationType;
        }

        public String getPointcutExpression() {
            return pointcutExpression;
        }

        @Override
        public String toString() {
            return "AspectJAnnotation{" +
                    "annotationType=" + annotationType +
                    ", pointcutExpression='" + pointcutExpression + '\'' +
                    '}';
        }
    }

}